MediaStore.java revision 955d8d69ea6caabce1461dc25b339b9bf9dc61a6
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            String filePath = null;
642            // Log.v(TAG, "getThumbnail: origId="+origId+", kind="+kind+", isVideo="+isVideo);
643            // If the magic is non-zero, we simply return thumbnail if it does exist.
644            // querying MediaProvider and simply return thumbnail.
645            MiniThumbFile thumbFile = new MiniThumbFile(isVideo ? Video.Media.EXTERNAL_CONTENT_URI
646                    : Images.Media.EXTERNAL_CONTENT_URI);
647            Cursor c = null;
648            try {
649                long magic = thumbFile.getMagic(origId);
650                if (magic != 0) {
651                    if (kind == MICRO_KIND) {
652                        synchronized (sThumbBufLock) {
653                            if (sThumbBuf == null) {
654                                sThumbBuf = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
655                            }
656                            if (thumbFile.getMiniThumbFromFile(origId, sThumbBuf) != null) {
657                                bitmap = BitmapFactory.decodeByteArray(sThumbBuf, 0, sThumbBuf.length);
658                                if (bitmap == null) {
659                                    Log.w(TAG, "couldn't decode byte array.");
660                                }
661                            }
662                        }
663                        return bitmap;
664                    } else if (kind == MINI_KIND) {
665                        String column = isVideo ? "video_id=" : "image_id=";
666                        c = cr.query(baseUri, PROJECTION, column + origId, null, null);
667                        if (c != null && c.moveToFirst()) {
668                            bitmap = getMiniThumbFromFile(c, baseUri, cr, options);
669                            if (bitmap != null) {
670                                return bitmap;
671                            }
672                        }
673                    }
674                }
675
676                Uri blockingUri = baseUri.buildUpon().appendQueryParameter("blocking", "1")
677                        .appendQueryParameter("orig_id", String.valueOf(origId))
678                        .appendQueryParameter("group_id", String.valueOf(groupId)).build();
679                if (c != null) c.close();
680                c = cr.query(blockingUri, PROJECTION, null, null, null);
681                // This happens when original image/video doesn't exist.
682                if (c == null) return null;
683
684                // Assuming thumbnail has been generated, at least original image exists.
685                if (kind == MICRO_KIND) {
686                    synchronized (sThumbBufLock) {
687                        if (sThumbBuf == null) {
688                            sThumbBuf = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
689                        }
690                        Arrays.fill(sThumbBuf, (byte)0);
691                        if (thumbFile.getMiniThumbFromFile(origId, sThumbBuf) != null) {
692                            bitmap = BitmapFactory.decodeByteArray(sThumbBuf, 0, sThumbBuf.length);
693                            if (bitmap == null) {
694                                Log.w(TAG, "couldn't decode byte array.");
695                            }
696                        }
697                    }
698                } else if (kind == MINI_KIND) {
699                    if (c.moveToFirst()) {
700                        bitmap = getMiniThumbFromFile(c, baseUri, cr, options);
701                    }
702                } else {
703                    throw new IllegalArgumentException("Unsupported kind: " + kind);
704                }
705
706                // We probably run out of space, so create the thumbnail in memory.
707                if (bitmap == null) {
708                    Log.v(TAG, "Create the thumbnail in memory: origId=" + origId
709                            + ", kind=" + kind + ", isVideo="+isVideo);
710                    Uri uri = Uri.parse(
711                            baseUri.buildUpon().appendPath(String.valueOf(origId))
712                                    .toString().replaceFirst("thumbnails", "media"));
713                    if (filePath == null) {
714                        if (c != null) c.close();
715                        c = cr.query(uri, PROJECTION, null, null, null);
716                        if (c == null || !c.moveToFirst()) {
717                            return null;
718                        }
719                        filePath = c.getString(1);
720                    }
721                    if (isVideo) {
722                        bitmap = ThumbnailUtils.createVideoThumbnail(filePath, kind);
723                    } else {
724                        bitmap = ThumbnailUtils.createImageThumbnail(filePath, kind);
725                    }
726                }
727            } catch (SQLiteException ex) {
728                Log.w(TAG, ex);
729            } finally {
730                if (c != null) c.close();
731                // To avoid file descriptor leak in application process.
732                thumbFile.deactivate();
733                thumbFile = null;
734            }
735            return bitmap;
736        }
737    }
738
739    /**
740     * Contains meta data for all available images.
741     */
742    public static final class Images {
743        public interface ImageColumns extends MediaColumns {
744            /**
745             * The description of the image
746             * <P>Type: TEXT</P>
747             */
748            public static final String DESCRIPTION = "description";
749
750            /**
751             * The picasa id of the image
752             * <P>Type: TEXT</P>
753             */
754            public static final String PICASA_ID = "picasa_id";
755
756            /**
757             * Whether the video should be published as public or private
758             * <P>Type: INTEGER</P>
759             */
760            public static final String IS_PRIVATE = "isprivate";
761
762            /**
763             * The latitude where the image was captured.
764             * <P>Type: DOUBLE</P>
765             */
766            public static final String LATITUDE = "latitude";
767
768            /**
769             * The longitude where the image was captured.
770             * <P>Type: DOUBLE</P>
771             */
772            public static final String LONGITUDE = "longitude";
773
774            /**
775             * The date & time that the image was taken in units
776             * of milliseconds since jan 1, 1970.
777             * <P>Type: INTEGER</P>
778             */
779            public static final String DATE_TAKEN = "datetaken";
780
781            /**
782             * The orientation for the image expressed as degrees.
783             * Only degrees 0, 90, 180, 270 will work.
784             * <P>Type: INTEGER</P>
785             */
786            public static final String ORIENTATION = "orientation";
787
788            /**
789             * The mini thumb id.
790             * <P>Type: INTEGER</P>
791             */
792            public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
793
794            /**
795             * The bucket id of the image. This is a read-only property that
796             * is automatically computed from the DATA column.
797             * <P>Type: TEXT</P>
798             */
799            public static final String BUCKET_ID = "bucket_id";
800
801            /**
802             * The bucket display name of the image. This is a read-only property that
803             * is automatically computed from the DATA column.
804             * <P>Type: TEXT</P>
805             */
806            public static final String BUCKET_DISPLAY_NAME = "bucket_display_name";
807        }
808
809        public static final class Media implements ImageColumns {
810            public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
811                return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
812            }
813
814            public static final Cursor query(ContentResolver cr, Uri uri, String[] projection,
815                    String where, String orderBy) {
816                return cr.query(uri, projection, where,
817                                             null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
818            }
819
820            public static final Cursor query(ContentResolver cr, Uri uri, String[] projection,
821                    String selection, String [] selectionArgs, String orderBy) {
822                return cr.query(uri, projection, selection,
823                        selectionArgs, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
824            }
825
826            /**
827             * Retrieves an image for the given url as a {@link Bitmap}.
828             *
829             * @param cr The content resolver to use
830             * @param url The url of the image
831             * @throws FileNotFoundException
832             * @throws IOException
833             */
834            public static final Bitmap getBitmap(ContentResolver cr, Uri url)
835                    throws FileNotFoundException, IOException {
836                InputStream input = cr.openInputStream(url);
837                Bitmap bitmap = BitmapFactory.decodeStream(input);
838                input.close();
839                return bitmap;
840            }
841
842            /**
843             * Insert an image and create a thumbnail for it.
844             *
845             * @param cr The content resolver to use
846             * @param imagePath The path to the image to insert
847             * @param name The name of the image
848             * @param description The description of the image
849             * @return The URL to the newly created image
850             * @throws FileNotFoundException
851             */
852            public static final String insertImage(ContentResolver cr, String imagePath,
853                    String name, String description) throws FileNotFoundException {
854                // Check if file exists with a FileInputStream
855                FileInputStream stream = new FileInputStream(imagePath);
856                try {
857                    Bitmap bm = BitmapFactory.decodeFile(imagePath);
858                    String ret = insertImage(cr, bm, name, description);
859                    bm.recycle();
860                    return ret;
861                } finally {
862                    try {
863                        stream.close();
864                    } catch (IOException e) {
865                    }
866                }
867            }
868
869            private static final Bitmap StoreThumbnail(
870                    ContentResolver cr,
871                    Bitmap source,
872                    long id,
873                    float width, float height,
874                    int kind) {
875                // create the matrix to scale it
876                Matrix matrix = new Matrix();
877
878                float scaleX = width / source.getWidth();
879                float scaleY = height / source.getHeight();
880
881                matrix.setScale(scaleX, scaleY);
882
883                Bitmap thumb = Bitmap.createBitmap(source, 0, 0,
884                                                   source.getWidth(),
885                                                   source.getHeight(), matrix,
886                                                   true);
887
888                ContentValues values = new ContentValues(4);
889                values.put(Images.Thumbnails.KIND,     kind);
890                values.put(Images.Thumbnails.IMAGE_ID, (int)id);
891                values.put(Images.Thumbnails.HEIGHT,   thumb.getHeight());
892                values.put(Images.Thumbnails.WIDTH,    thumb.getWidth());
893
894                Uri url = cr.insert(Images.Thumbnails.EXTERNAL_CONTENT_URI, values);
895
896                try {
897                    OutputStream thumbOut = cr.openOutputStream(url);
898
899                    thumb.compress(Bitmap.CompressFormat.JPEG, 100, thumbOut);
900                    thumbOut.close();
901                    return thumb;
902                }
903                catch (FileNotFoundException ex) {
904                    return null;
905                }
906                catch (IOException ex) {
907                    return null;
908                }
909            }
910
911            /**
912             * Insert an image and create a thumbnail for it.
913             *
914             * @param cr The content resolver to use
915             * @param source The stream to use for the image
916             * @param title The name of the image
917             * @param description The description of the image
918             * @return The URL to the newly created image, or <code>null</code> if the image failed to be stored
919             *              for any reason.
920             */
921            public static final String insertImage(ContentResolver cr, Bitmap source,
922                                                   String title, String description) {
923                ContentValues values = new ContentValues();
924                values.put(Images.Media.TITLE, title);
925                values.put(Images.Media.DESCRIPTION, description);
926                values.put(Images.Media.MIME_TYPE, "image/jpeg");
927
928                Uri url = null;
929                String stringUrl = null;    /* value to be returned */
930
931                try {
932                    url = cr.insert(EXTERNAL_CONTENT_URI, values);
933
934                    if (source != null) {
935                        OutputStream imageOut = cr.openOutputStream(url);
936                        try {
937                            source.compress(Bitmap.CompressFormat.JPEG, 50, imageOut);
938                        } finally {
939                            imageOut.close();
940                        }
941
942                        long id = ContentUris.parseId(url);
943                        // Wait until MINI_KIND thumbnail is generated.
944                        Bitmap miniThumb = Images.Thumbnails.getThumbnail(cr, id,
945                                Images.Thumbnails.MINI_KIND, null);
946                        // This is for backward compatibility.
947                        Bitmap microThumb = StoreThumbnail(cr, miniThumb, id, 50F, 50F,
948                                Images.Thumbnails.MICRO_KIND);
949                    } else {
950                        Log.e(TAG, "Failed to create thumbnail, removing original");
951                        cr.delete(url, null, null);
952                        url = null;
953                    }
954                } catch (Exception e) {
955                    Log.e(TAG, "Failed to insert image", e);
956                    if (url != null) {
957                        cr.delete(url, null, null);
958                        url = null;
959                    }
960                }
961
962                if (url != null) {
963                    stringUrl = url.toString();
964                }
965
966                return stringUrl;
967            }
968
969            /**
970             * Get the content:// style URI for the image media table on the
971             * given volume.
972             *
973             * @param volumeName the name of the volume to get the URI for
974             * @return the URI to the image media table on the given volume
975             */
976            public static Uri getContentUri(String volumeName) {
977                return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
978                        "/images/media");
979            }
980
981            /**
982             * The content:// style URI for the internal storage.
983             */
984            public static final Uri INTERNAL_CONTENT_URI =
985                    getContentUri("internal");
986
987            /**
988             * The content:// style URI for the "primary" external storage
989             * volume.
990             */
991            public static final Uri EXTERNAL_CONTENT_URI =
992                    getContentUri("external");
993
994            /**
995             * The MIME type of of this directory of
996             * images.  Note that each entry in this directory will have a standard
997             * image MIME type as appropriate -- for example, image/jpeg.
998             */
999            public static final String CONTENT_TYPE = "vnd.android.cursor.dir/image";
1000
1001            /**
1002             * The default sort order for this table
1003             */
1004            public static final String DEFAULT_SORT_ORDER = ImageColumns.BUCKET_DISPLAY_NAME;
1005        }
1006
1007        /**
1008         * This class allows developers to query and get two kinds of thumbnails:
1009         * MINI_KIND: 512 x 384 thumbnail
1010         * MICRO_KIND: 96 x 96 thumbnail
1011         */
1012        public static class Thumbnails implements BaseColumns {
1013            public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
1014                return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
1015            }
1016
1017            public static final Cursor queryMiniThumbnails(ContentResolver cr, Uri uri, int kind,
1018                    String[] projection) {
1019                return cr.query(uri, projection, "kind = " + kind, null, DEFAULT_SORT_ORDER);
1020            }
1021
1022            public static final Cursor queryMiniThumbnail(ContentResolver cr, long origId, int kind,
1023                    String[] projection) {
1024                return cr.query(EXTERNAL_CONTENT_URI, projection,
1025                        IMAGE_ID + " = " + origId + " AND " + KIND + " = " +
1026                        kind, null, null);
1027            }
1028
1029            /**
1030             * This method cancels the thumbnail request so clients waiting for getThumbnail will be
1031             * interrupted and return immediately. Only the original process which made the getThumbnail
1032             * requests can cancel their own requests.
1033             *
1034             * @param cr ContentResolver
1035             * @param origId original image id
1036             */
1037            public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
1038                InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI,
1039                        InternalThumbnails.DEFAULT_GROUP_ID);
1040            }
1041
1042            /**
1043             * This method checks if the thumbnails of the specified image (origId) has been created.
1044             * It will be blocked until the thumbnails are generated.
1045             *
1046             * @param cr ContentResolver used to dispatch queries to MediaProvider.
1047             * @param origId Original image id associated with thumbnail of interest.
1048             * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
1049             * @param options this is only used for MINI_KIND when decoding the Bitmap
1050             * @return A Bitmap instance. It could be null if the original image
1051             *         associated with origId doesn't exist or memory is not enough.
1052             */
1053            public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
1054                    BitmapFactory.Options options) {
1055                return InternalThumbnails.getThumbnail(cr, origId,
1056                        InternalThumbnails.DEFAULT_GROUP_ID, kind, options,
1057                        EXTERNAL_CONTENT_URI, false);
1058            }
1059
1060            /**
1061             * This method cancels the thumbnail request so clients waiting for getThumbnail will be
1062             * interrupted and return immediately. Only the original process which made the getThumbnail
1063             * requests can cancel their own requests.
1064             *
1065             * @param cr ContentResolver
1066             * @param origId original image id
1067             * @param groupId the same groupId used in getThumbnail.
1068             */
1069            public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) {
1070                InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId);
1071            }
1072
1073            /**
1074             * This method checks if the thumbnails of the specified image (origId) has been created.
1075             * It will be blocked until the thumbnails are generated.
1076             *
1077             * @param cr ContentResolver used to dispatch queries to MediaProvider.
1078             * @param origId Original image id associated with thumbnail of interest.
1079             * @param groupId the id of group to which this request belongs
1080             * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
1081             * @param options this is only used for MINI_KIND when decoding the Bitmap
1082             * @return A Bitmap instance. It could be null if the original image
1083             *         associated with origId doesn't exist or memory is not enough.
1084             */
1085            public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId,
1086                    int kind, BitmapFactory.Options options) {
1087                return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options,
1088                        EXTERNAL_CONTENT_URI, false);
1089            }
1090
1091            /**
1092             * Get the content:// style URI for the image media table on the
1093             * given volume.
1094             *
1095             * @param volumeName the name of the volume to get the URI for
1096             * @return the URI to the image media table on the given volume
1097             */
1098            public static Uri getContentUri(String volumeName) {
1099                return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
1100                        "/images/thumbnails");
1101            }
1102
1103            /**
1104             * The content:// style URI for the internal storage.
1105             */
1106            public static final Uri INTERNAL_CONTENT_URI =
1107                    getContentUri("internal");
1108
1109            /**
1110             * The content:// style URI for the "primary" external storage
1111             * volume.
1112             */
1113            public static final Uri EXTERNAL_CONTENT_URI =
1114                    getContentUri("external");
1115
1116            /**
1117             * The default sort order for this table
1118             */
1119            public static final String DEFAULT_SORT_ORDER = "image_id ASC";
1120
1121            /**
1122             * The data stream for the thumbnail
1123             * <P>Type: DATA STREAM</P>
1124             */
1125            public static final String DATA = "_data";
1126
1127            /**
1128             * The original image for the thumbnal
1129             * <P>Type: INTEGER (ID from Images table)</P>
1130             */
1131            public static final String IMAGE_ID = "image_id";
1132
1133            /**
1134             * The kind of the thumbnail
1135             * <P>Type: INTEGER (One of the values below)</P>
1136             */
1137            public static final String KIND = "kind";
1138
1139            public static final int MINI_KIND = 1;
1140            public static final int FULL_SCREEN_KIND = 2;
1141            public static final int MICRO_KIND = 3;
1142            /**
1143             * The blob raw data of thumbnail
1144             * <P>Type: DATA STREAM</P>
1145             */
1146            public static final String THUMB_DATA = "thumb_data";
1147
1148            /**
1149             * The width of the thumbnal
1150             * <P>Type: INTEGER (long)</P>
1151             */
1152            public static final String WIDTH = "width";
1153
1154            /**
1155             * The height of the thumbnail
1156             * <P>Type: INTEGER (long)</P>
1157             */
1158            public static final String HEIGHT = "height";
1159        }
1160    }
1161
1162    /**
1163     * Container for all audio content.
1164     */
1165    public static final class Audio {
1166        /**
1167         * Columns for audio file that show up in multiple tables.
1168         */
1169        public interface AudioColumns extends MediaColumns {
1170
1171            /**
1172             * A non human readable key calculated from the TITLE, used for
1173             * searching, sorting and grouping
1174             * <P>Type: TEXT</P>
1175             */
1176            public static final String TITLE_KEY = "title_key";
1177
1178            /**
1179             * The duration of the audio file, in ms
1180             * <P>Type: INTEGER (long)</P>
1181             */
1182            public static final String DURATION = "duration";
1183
1184            /**
1185             * The position, in ms, playback was at when playback for this file
1186             * was last stopped.
1187             * <P>Type: INTEGER (long)</P>
1188             */
1189            public static final String BOOKMARK = "bookmark";
1190
1191            /**
1192             * The id of the artist who created the audio file, if any
1193             * <P>Type: INTEGER (long)</P>
1194             */
1195            public static final String ARTIST_ID = "artist_id";
1196
1197            /**
1198             * The artist who created the audio file, if any
1199             * <P>Type: TEXT</P>
1200             */
1201            public static final String ARTIST = "artist";
1202
1203            /**
1204             * The artist credited for the album that contains the audio file
1205             * <P>Type: TEXT</P>
1206             * @hide
1207             */
1208            public static final String ALBUM_ARTIST = "album_artist";
1209
1210            /**
1211             * Whether the song is part of a compilation
1212             * <P>Type: TEXT</P>
1213             * @hide
1214             */
1215            public static final String COMPILATION = "compilation";
1216
1217            /**
1218             * A non human readable key calculated from the ARTIST, used for
1219             * searching, sorting and grouping
1220             * <P>Type: TEXT</P>
1221             */
1222            public static final String ARTIST_KEY = "artist_key";
1223
1224            /**
1225             * The composer of the audio file, if any
1226             * <P>Type: TEXT</P>
1227             */
1228            public static final String COMPOSER = "composer";
1229
1230            /**
1231             * The id of the album the audio file is from, if any
1232             * <P>Type: INTEGER (long)</P>
1233             */
1234            public static final String ALBUM_ID = "album_id";
1235
1236            /**
1237             * The album the audio file is from, if any
1238             * <P>Type: TEXT</P>
1239             */
1240            public static final String ALBUM = "album";
1241
1242            /**
1243             * A non human readable key calculated from the ALBUM, used for
1244             * searching, sorting and grouping
1245             * <P>Type: TEXT</P>
1246             */
1247            public static final String ALBUM_KEY = "album_key";
1248
1249            /**
1250             * The track number of this song on the album, if any.
1251             * This number encodes both the track number and the
1252             * disc number. For multi-disc sets, this number will
1253             * be 1xxx for tracks on the first disc, 2xxx for tracks
1254             * on the second disc, etc.
1255             * <P>Type: INTEGER</P>
1256             */
1257            public static final String TRACK = "track";
1258
1259            /**
1260             * The year the audio file was recorded, if any
1261             * <P>Type: INTEGER</P>
1262             */
1263            public static final String YEAR = "year";
1264
1265            /**
1266             * Non-zero if the audio file is music
1267             * <P>Type: INTEGER (boolean)</P>
1268             */
1269            public static final String IS_MUSIC = "is_music";
1270
1271            /**
1272             * Non-zero if the audio file is a podcast
1273             * <P>Type: INTEGER (boolean)</P>
1274             */
1275            public static final String IS_PODCAST = "is_podcast";
1276
1277            /**
1278             * Non-zero if the audio file may be a ringtone
1279             * <P>Type: INTEGER (boolean)</P>
1280             */
1281            public static final String IS_RINGTONE = "is_ringtone";
1282
1283            /**
1284             * Non-zero if the audio file may be an alarm
1285             * <P>Type: INTEGER (boolean)</P>
1286             */
1287            public static final String IS_ALARM = "is_alarm";
1288
1289            /**
1290             * Non-zero if the audio file may be a notification sound
1291             * <P>Type: INTEGER (boolean)</P>
1292             */
1293            public static final String IS_NOTIFICATION = "is_notification";
1294
1295            /**
1296             * The genre of the audio file, if any
1297             * <P>Type: TEXT</P>
1298             * Does not exist in the database - only used by the media scanner for inserts.
1299             * @hide
1300             */
1301            public static final String GENRE = "genre";
1302        }
1303
1304        /**
1305         * Converts a name to a "key" that can be used for grouping, sorting
1306         * and searching.
1307         * The rules that govern this conversion are:
1308         * - remove 'special' characters like ()[]'!?.,
1309         * - remove leading/trailing spaces
1310         * - convert everything to lowercase
1311         * - remove leading "the ", "an " and "a "
1312         * - remove trailing ", the|an|a"
1313         * - remove accents. This step leaves us with CollationKey data,
1314         *   which is not human readable
1315         *
1316         * @param name The artist or album name to convert
1317         * @return The "key" for the given name.
1318         */
1319        public static String keyFor(String name) {
1320            if (name != null)  {
1321                boolean sortfirst = false;
1322                if (name.equals(UNKNOWN_STRING)) {
1323                    return "\001";
1324                }
1325                // Check if the first character is \001. We use this to
1326                // force sorting of certain special files, like the silent ringtone.
1327                if (name.startsWith("\001")) {
1328                    sortfirst = true;
1329                }
1330                name = name.trim().toLowerCase();
1331                if (name.startsWith("the ")) {
1332                    name = name.substring(4);
1333                }
1334                if (name.startsWith("an ")) {
1335                    name = name.substring(3);
1336                }
1337                if (name.startsWith("a ")) {
1338                    name = name.substring(2);
1339                }
1340                if (name.endsWith(", the") || name.endsWith(",the") ||
1341                    name.endsWith(", an") || name.endsWith(",an") ||
1342                    name.endsWith(", a") || name.endsWith(",a")) {
1343                    name = name.substring(0, name.lastIndexOf(','));
1344                }
1345                name = name.replaceAll("[\\[\\]\\(\\)\"'.,?!]", "").trim();
1346                if (name.length() > 0) {
1347                    // Insert a separator between the characters to avoid
1348                    // matches on a partial character. If we ever change
1349                    // to start-of-word-only matches, this can be removed.
1350                    StringBuilder b = new StringBuilder();
1351                    b.append('.');
1352                    int nl = name.length();
1353                    for (int i = 0; i < nl; i++) {
1354                        b.append(name.charAt(i));
1355                        b.append('.');
1356                    }
1357                    name = b.toString();
1358                    String key = DatabaseUtils.getCollationKey(name);
1359                    if (sortfirst) {
1360                        key = "\001" + key;
1361                    }
1362                    return key;
1363               } else {
1364                    return "";
1365                }
1366            }
1367            return null;
1368        }
1369
1370        public static final class Media implements AudioColumns {
1371
1372            private static final String[] EXTERNAL_PATHS;
1373
1374            static {
1375                String secondary_storage = System.getenv("SECONDARY_STORAGE");
1376                if (secondary_storage != null) {
1377                    EXTERNAL_PATHS = secondary_storage.split(":");
1378                } else {
1379                    EXTERNAL_PATHS = new String[0];
1380                }
1381            }
1382
1383            /**
1384             * Get the content:// style URI for the audio media table on the
1385             * given volume.
1386             *
1387             * @param volumeName the name of the volume to get the URI for
1388             * @return the URI to the audio media table on the given volume
1389             */
1390            public static Uri getContentUri(String volumeName) {
1391                return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
1392                        "/audio/media");
1393            }
1394
1395            public static Uri getContentUriForPath(String path) {
1396                for (String ep : EXTERNAL_PATHS) {
1397                    if (path.startsWith(ep)) {
1398                        return EXTERNAL_CONTENT_URI;
1399                    }
1400                }
1401
1402                return (path.startsWith(Environment.getExternalStorageDirectory().getPath()) ?
1403                        EXTERNAL_CONTENT_URI : INTERNAL_CONTENT_URI);
1404            }
1405
1406            /**
1407             * The content:// style URI for the internal storage.
1408             */
1409            public static final Uri INTERNAL_CONTENT_URI =
1410                    getContentUri("internal");
1411
1412            /**
1413             * The content:// style URI for the "primary" external storage
1414             * volume.
1415             */
1416            public static final Uri EXTERNAL_CONTENT_URI =
1417                    getContentUri("external");
1418
1419            /**
1420             * The MIME type for this table.
1421             */
1422            public static final String CONTENT_TYPE = "vnd.android.cursor.dir/audio";
1423
1424            /**
1425             * The MIME type for an audio track.
1426             */
1427            public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/audio";
1428
1429            /**
1430             * The default sort order for this table
1431             */
1432            public static final String DEFAULT_SORT_ORDER = TITLE_KEY;
1433
1434            /**
1435             * Activity Action: Start SoundRecorder application.
1436             * <p>Input: nothing.
1437             * <p>Output: An uri to the recorded sound stored in the Media Library
1438             * if the recording was successful.
1439             * May also contain the extra EXTRA_MAX_BYTES.
1440             * @see #EXTRA_MAX_BYTES
1441             */
1442            public static final String RECORD_SOUND_ACTION =
1443                    "android.provider.MediaStore.RECORD_SOUND";
1444
1445            /**
1446             * The name of the Intent-extra used to define a maximum file size for
1447             * a recording made by the SoundRecorder application.
1448             *
1449             * @see #RECORD_SOUND_ACTION
1450             */
1451             public static final String EXTRA_MAX_BYTES =
1452                    "android.provider.MediaStore.extra.MAX_BYTES";
1453        }
1454
1455        /**
1456         * Columns representing an audio genre
1457         */
1458        public interface GenresColumns {
1459            /**
1460             * The name of the genre
1461             * <P>Type: TEXT</P>
1462             */
1463            public static final String NAME = "name";
1464        }
1465
1466        /**
1467         * Contains all genres for audio files
1468         */
1469        public static final class Genres implements BaseColumns, GenresColumns {
1470            /**
1471             * Get the content:// style URI for the audio genres table on the
1472             * given volume.
1473             *
1474             * @param volumeName the name of the volume to get the URI for
1475             * @return the URI to the audio genres table on the given volume
1476             */
1477            public static Uri getContentUri(String volumeName) {
1478                return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
1479                        "/audio/genres");
1480            }
1481
1482            /**
1483             * Get the content:// style URI for querying the genres of an audio file.
1484             *
1485             * @param volumeName the name of the volume to get the URI for
1486             * @param audioId the ID of the audio file for which to retrieve the genres
1487             * @return the URI to for querying the genres for the audio file
1488             * with the given the volume and audioID
1489             */
1490            public static Uri getContentUriForAudioId(String volumeName, int audioId) {
1491                return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
1492                        "/audio/media/" + audioId + "/genres");
1493            }
1494
1495            /**
1496             * The content:// style URI for the internal storage.
1497             */
1498            public static final Uri INTERNAL_CONTENT_URI =
1499                    getContentUri("internal");
1500
1501            /**
1502             * The content:// style URI for the "primary" external storage
1503             * volume.
1504             */
1505            public static final Uri EXTERNAL_CONTENT_URI =
1506                    getContentUri("external");
1507
1508            /**
1509             * The MIME type for this table.
1510             */
1511            public static final String CONTENT_TYPE = "vnd.android.cursor.dir/genre";
1512
1513            /**
1514             * The MIME type for entries in this table.
1515             */
1516            public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/genre";
1517
1518            /**
1519             * The default sort order for this table
1520             */
1521            public static final String DEFAULT_SORT_ORDER = NAME;
1522
1523            /**
1524             * Sub-directory of each genre containing all members.
1525             */
1526            public static final class Members implements AudioColumns {
1527
1528                public static final Uri getContentUri(String volumeName,
1529                        long genreId) {
1530                    return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
1531                            + "/audio/genres/" + genreId + "/members");
1532                }
1533
1534                /**
1535                 * A subdirectory of each genre containing all member audio files.
1536                 */
1537                public static final String CONTENT_DIRECTORY = "members";
1538
1539                /**
1540                 * The default sort order for this table
1541                 */
1542                public static final String DEFAULT_SORT_ORDER = TITLE_KEY;
1543
1544                /**
1545                 * The ID of the audio file
1546                 * <P>Type: INTEGER (long)</P>
1547                 */
1548                public static final String AUDIO_ID = "audio_id";
1549
1550                /**
1551                 * The ID of the genre
1552                 * <P>Type: INTEGER (long)</P>
1553                 */
1554                public static final String GENRE_ID = "genre_id";
1555            }
1556        }
1557
1558        /**
1559         * Columns representing a playlist
1560         */
1561        public interface PlaylistsColumns {
1562            /**
1563             * The name of the playlist
1564             * <P>Type: TEXT</P>
1565             */
1566            public static final String NAME = "name";
1567
1568            /**
1569             * The data stream for the playlist file
1570             * <P>Type: DATA STREAM</P>
1571             */
1572            public static final String DATA = "_data";
1573
1574            /**
1575             * The time the file was added to the media provider
1576             * Units are seconds since 1970.
1577             * <P>Type: INTEGER (long)</P>
1578             */
1579            public static final String DATE_ADDED = "date_added";
1580
1581            /**
1582             * The time the file was last modified
1583             * Units are seconds since 1970.
1584             * NOTE: This is for internal use by the media scanner.  Do not modify this field.
1585             * <P>Type: INTEGER (long)</P>
1586             */
1587            public static final String DATE_MODIFIED = "date_modified";
1588        }
1589
1590        /**
1591         * Contains playlists for audio files
1592         */
1593        public static final class Playlists implements BaseColumns,
1594                PlaylistsColumns {
1595            /**
1596             * Get the content:// style URI for the audio playlists table on the
1597             * given volume.
1598             *
1599             * @param volumeName the name of the volume to get the URI for
1600             * @return the URI to the audio playlists table on the given volume
1601             */
1602            public static Uri getContentUri(String volumeName) {
1603                return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
1604                        "/audio/playlists");
1605            }
1606
1607            /**
1608             * The content:// style URI for the internal storage.
1609             */
1610            public static final Uri INTERNAL_CONTENT_URI =
1611                    getContentUri("internal");
1612
1613            /**
1614             * The content:// style URI for the "primary" external storage
1615             * volume.
1616             */
1617            public static final Uri EXTERNAL_CONTENT_URI =
1618                    getContentUri("external");
1619
1620            /**
1621             * The MIME type for this table.
1622             */
1623            public static final String CONTENT_TYPE = "vnd.android.cursor.dir/playlist";
1624
1625            /**
1626             * The MIME type for entries in this table.
1627             */
1628            public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/playlist";
1629
1630            /**
1631             * The default sort order for this table
1632             */
1633            public static final String DEFAULT_SORT_ORDER = NAME;
1634
1635            /**
1636             * Sub-directory of each playlist containing all members.
1637             */
1638            public static final class Members implements AudioColumns {
1639                public static final Uri getContentUri(String volumeName,
1640                        long playlistId) {
1641                    return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
1642                            + "/audio/playlists/" + playlistId + "/members");
1643                }
1644
1645                /**
1646                 * Convenience method to move a playlist item to a new location
1647                 * @param res The content resolver to use
1648                 * @param playlistId The numeric id of the playlist
1649                 * @param from The position of the item to move
1650                 * @param to The position to move the item to
1651                 * @return true on success
1652                 */
1653                public static final boolean moveItem(ContentResolver res,
1654                        long playlistId, int from, int to) {
1655                    Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external",
1656                            playlistId)
1657                            .buildUpon()
1658                            .appendEncodedPath(String.valueOf(from))
1659                            .appendQueryParameter("move", "true")
1660                            .build();
1661                    ContentValues values = new ContentValues();
1662                    values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, to);
1663                    return res.update(uri, values, null, null) != 0;
1664                }
1665
1666                /**
1667                 * The ID within the playlist.
1668                 */
1669                public static final String _ID = "_id";
1670
1671                /**
1672                 * A subdirectory of each playlist containing all member audio
1673                 * files.
1674                 */
1675                public static final String CONTENT_DIRECTORY = "members";
1676
1677                /**
1678                 * The ID of the audio file
1679                 * <P>Type: INTEGER (long)</P>
1680                 */
1681                public static final String AUDIO_ID = "audio_id";
1682
1683                /**
1684                 * The ID of the playlist
1685                 * <P>Type: INTEGER (long)</P>
1686                 */
1687                public static final String PLAYLIST_ID = "playlist_id";
1688
1689                /**
1690                 * The order of the songs in the playlist
1691                 * <P>Type: INTEGER (long)></P>
1692                 */
1693                public static final String PLAY_ORDER = "play_order";
1694
1695                /**
1696                 * The default sort order for this table
1697                 */
1698                public static final String DEFAULT_SORT_ORDER = PLAY_ORDER;
1699            }
1700        }
1701
1702        /**
1703         * Columns representing an artist
1704         */
1705        public interface ArtistColumns {
1706            /**
1707             * The artist who created the audio file, if any
1708             * <P>Type: TEXT</P>
1709             */
1710            public static final String ARTIST = "artist";
1711
1712            /**
1713             * A non human readable key calculated from the ARTIST, used for
1714             * searching, sorting and grouping
1715             * <P>Type: TEXT</P>
1716             */
1717            public static final String ARTIST_KEY = "artist_key";
1718
1719            /**
1720             * The number of albums in the database for this artist
1721             */
1722            public static final String NUMBER_OF_ALBUMS = "number_of_albums";
1723
1724            /**
1725             * The number of albums in the database for this artist
1726             */
1727            public static final String NUMBER_OF_TRACKS = "number_of_tracks";
1728        }
1729
1730        /**
1731         * Contains artists for audio files
1732         */
1733        public static final class Artists implements BaseColumns, ArtistColumns {
1734            /**
1735             * Get the content:// style URI for the artists table on the
1736             * given volume.
1737             *
1738             * @param volumeName the name of the volume to get the URI for
1739             * @return the URI to the audio artists table on the given volume
1740             */
1741            public static Uri getContentUri(String volumeName) {
1742                return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
1743                        "/audio/artists");
1744            }
1745
1746            /**
1747             * The content:// style URI for the internal storage.
1748             */
1749            public static final Uri INTERNAL_CONTENT_URI =
1750                    getContentUri("internal");
1751
1752            /**
1753             * The content:// style URI for the "primary" external storage
1754             * volume.
1755             */
1756            public static final Uri EXTERNAL_CONTENT_URI =
1757                    getContentUri("external");
1758
1759            /**
1760             * The MIME type for this table.
1761             */
1762            public static final String CONTENT_TYPE = "vnd.android.cursor.dir/artists";
1763
1764            /**
1765             * The MIME type for entries in this table.
1766             */
1767            public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/artist";
1768
1769            /**
1770             * The default sort order for this table
1771             */
1772            public static final String DEFAULT_SORT_ORDER = ARTIST_KEY;
1773
1774            /**
1775             * Sub-directory of each artist containing all albums on which
1776             * a song by the artist appears.
1777             */
1778            public static final class Albums implements AlbumColumns {
1779                public static final Uri getContentUri(String volumeName,
1780                        long artistId) {
1781                    return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
1782                            + "/audio/artists/" + artistId + "/albums");
1783                }
1784            }
1785        }
1786
1787        /**
1788         * Columns representing an album
1789         */
1790        public interface AlbumColumns {
1791
1792            /**
1793             * The id for the album
1794             * <P>Type: INTEGER</P>
1795             */
1796            public static final String ALBUM_ID = "album_id";
1797
1798            /**
1799             * The album on which the audio file appears, if any
1800             * <P>Type: TEXT</P>
1801             */
1802            public static final String ALBUM = "album";
1803
1804            /**
1805             * The artist whose songs appear on this album
1806             * <P>Type: TEXT</P>
1807             */
1808            public static final String ARTIST = "artist";
1809
1810            /**
1811             * The number of songs on this album
1812             * <P>Type: INTEGER</P>
1813             */
1814            public static final String NUMBER_OF_SONGS = "numsongs";
1815
1816            /**
1817             * This column is available when getting album info via artist,
1818             * and indicates the number of songs on the album by the given
1819             * artist.
1820             * <P>Type: INTEGER</P>
1821             */
1822            public static final String NUMBER_OF_SONGS_FOR_ARTIST = "numsongs_by_artist";
1823
1824            /**
1825             * The year in which the earliest songs
1826             * on this album were released. This will often
1827             * be the same as {@link #LAST_YEAR}, but for compilation albums
1828             * they might differ.
1829             * <P>Type: INTEGER</P>
1830             */
1831            public static final String FIRST_YEAR = "minyear";
1832
1833            /**
1834             * The year in which the latest songs
1835             * on this album were released. This will often
1836             * be the same as {@link #FIRST_YEAR}, but for compilation albums
1837             * they might differ.
1838             * <P>Type: INTEGER</P>
1839             */
1840            public static final String LAST_YEAR = "maxyear";
1841
1842            /**
1843             * A non human readable key calculated from the ALBUM, used for
1844             * searching, sorting and grouping
1845             * <P>Type: TEXT</P>
1846             */
1847            public static final String ALBUM_KEY = "album_key";
1848
1849            /**
1850             * Cached album art.
1851             * <P>Type: TEXT</P>
1852             */
1853            public static final String ALBUM_ART = "album_art";
1854        }
1855
1856        /**
1857         * Contains artists for audio files
1858         */
1859        public static final class Albums implements BaseColumns, AlbumColumns {
1860            /**
1861             * Get the content:// style URI for the albums table on the
1862             * given volume.
1863             *
1864             * @param volumeName the name of the volume to get the URI for
1865             * @return the URI to the audio albums table on the given volume
1866             */
1867            public static Uri getContentUri(String volumeName) {
1868                return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
1869                        "/audio/albums");
1870            }
1871
1872            /**
1873             * The content:// style URI for the internal storage.
1874             */
1875            public static final Uri INTERNAL_CONTENT_URI =
1876                    getContentUri("internal");
1877
1878            /**
1879             * The content:// style URI for the "primary" external storage
1880             * volume.
1881             */
1882            public static final Uri EXTERNAL_CONTENT_URI =
1883                    getContentUri("external");
1884
1885            /**
1886             * The MIME type for this table.
1887             */
1888            public static final String CONTENT_TYPE = "vnd.android.cursor.dir/albums";
1889
1890            /**
1891             * The MIME type for entries in this table.
1892             */
1893            public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/album";
1894
1895            /**
1896             * The default sort order for this table
1897             */
1898            public static final String DEFAULT_SORT_ORDER = ALBUM_KEY;
1899        }
1900
1901        public static final class Radio {
1902            /**
1903             * The MIME type for entries in this table.
1904             */
1905            public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/radio";
1906
1907            // Not instantiable.
1908            private Radio() { }
1909        }
1910    }
1911
1912    public static final class Video {
1913
1914        /**
1915         * The default sort order for this table.
1916         */
1917        public static final String DEFAULT_SORT_ORDER = MediaColumns.DISPLAY_NAME;
1918
1919        public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
1920            return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
1921        }
1922
1923        public interface VideoColumns extends MediaColumns {
1924
1925            /**
1926             * The duration of the video file, in ms
1927             * <P>Type: INTEGER (long)</P>
1928             */
1929            public static final String DURATION = "duration";
1930
1931            /**
1932             * The artist who created the video file, if any
1933             * <P>Type: TEXT</P>
1934             */
1935            public static final String ARTIST = "artist";
1936
1937            /**
1938             * The album the video file is from, if any
1939             * <P>Type: TEXT</P>
1940             */
1941            public static final String ALBUM = "album";
1942
1943            /**
1944             * The resolution of the video file, formatted as "XxY"
1945             * <P>Type: TEXT</P>
1946             */
1947            public static final String RESOLUTION = "resolution";
1948
1949            /**
1950             * The description of the video recording
1951             * <P>Type: TEXT</P>
1952             */
1953            public static final String DESCRIPTION = "description";
1954
1955            /**
1956             * Whether the video should be published as public or private
1957             * <P>Type: INTEGER</P>
1958             */
1959            public static final String IS_PRIVATE = "isprivate";
1960
1961            /**
1962             * The user-added tags associated with a video
1963             * <P>Type: TEXT</P>
1964             */
1965            public static final String TAGS = "tags";
1966
1967            /**
1968             * The YouTube category of the video
1969             * <P>Type: TEXT</P>
1970             */
1971            public static final String CATEGORY = "category";
1972
1973            /**
1974             * The language of the video
1975             * <P>Type: TEXT</P>
1976             */
1977            public static final String LANGUAGE = "language";
1978
1979            /**
1980             * The latitude where the video was captured.
1981             * <P>Type: DOUBLE</P>
1982             */
1983            public static final String LATITUDE = "latitude";
1984
1985            /**
1986             * The longitude where the video was captured.
1987             * <P>Type: DOUBLE</P>
1988             */
1989            public static final String LONGITUDE = "longitude";
1990
1991            /**
1992             * The date & time that the video was taken in units
1993             * of milliseconds since jan 1, 1970.
1994             * <P>Type: INTEGER</P>
1995             */
1996            public static final String DATE_TAKEN = "datetaken";
1997
1998            /**
1999             * The mini thumb id.
2000             * <P>Type: INTEGER</P>
2001             */
2002            public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
2003
2004            /**
2005             * The bucket id of the video. This is a read-only property that
2006             * is automatically computed from the DATA column.
2007             * <P>Type: TEXT</P>
2008             */
2009            public static final String BUCKET_ID = "bucket_id";
2010
2011            /**
2012             * The bucket display name of the video. This is a read-only property that
2013             * is automatically computed from the DATA column.
2014             * <P>Type: TEXT</P>
2015             */
2016            public static final String BUCKET_DISPLAY_NAME = "bucket_display_name";
2017
2018            /**
2019             * The bookmark for the video. Time in ms. Represents the location in the video that the
2020             * video should start playing at the next time it is opened. If the value is null or
2021             * out of the range 0..DURATION-1 then the video should start playing from the
2022             * beginning.
2023             * <P>Type: INTEGER</P>
2024             */
2025            public static final String BOOKMARK = "bookmark";
2026        }
2027
2028        public static final class Media implements VideoColumns {
2029            /**
2030             * Get the content:// style URI for the video media table on the
2031             * given volume.
2032             *
2033             * @param volumeName the name of the volume to get the URI for
2034             * @return the URI to the video media table on the given volume
2035             */
2036            public static Uri getContentUri(String volumeName) {
2037                return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
2038                        "/video/media");
2039            }
2040
2041            /**
2042             * The content:// style URI for the internal storage.
2043             */
2044            public static final Uri INTERNAL_CONTENT_URI =
2045                    getContentUri("internal");
2046
2047            /**
2048             * The content:// style URI for the "primary" external storage
2049             * volume.
2050             */
2051            public static final Uri EXTERNAL_CONTENT_URI =
2052                    getContentUri("external");
2053
2054            /**
2055             * The MIME type for this table.
2056             */
2057            public static final String CONTENT_TYPE = "vnd.android.cursor.dir/video";
2058
2059            /**
2060             * The default sort order for this table
2061             */
2062            public static final String DEFAULT_SORT_ORDER = TITLE;
2063        }
2064
2065        /**
2066         * This class allows developers to query and get two kinds of thumbnails:
2067         * MINI_KIND: 512 x 384 thumbnail
2068         * MICRO_KIND: 96 x 96 thumbnail
2069         *
2070         */
2071        public static class Thumbnails implements BaseColumns {
2072            /**
2073             * This method cancels the thumbnail request so clients waiting for getThumbnail will be
2074             * interrupted and return immediately. Only the original process which made the getThumbnail
2075             * requests can cancel their own requests.
2076             *
2077             * @param cr ContentResolver
2078             * @param origId original video id
2079             */
2080            public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
2081                InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI,
2082                        InternalThumbnails.DEFAULT_GROUP_ID);
2083            }
2084
2085            /**
2086             * This method checks if the thumbnails of the specified image (origId) has been created.
2087             * It will be blocked until the thumbnails are generated.
2088             *
2089             * @param cr ContentResolver used to dispatch queries to MediaProvider.
2090             * @param origId Original image id associated with thumbnail of interest.
2091             * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
2092             * @param options this is only used for MINI_KIND when decoding the Bitmap
2093             * @return A Bitmap instance. It could be null if the original image
2094             *         associated with origId doesn't exist or memory is not enough.
2095             */
2096            public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
2097                    BitmapFactory.Options options) {
2098                return InternalThumbnails.getThumbnail(cr, origId,
2099                        InternalThumbnails.DEFAULT_GROUP_ID, kind, options,
2100                        EXTERNAL_CONTENT_URI, true);
2101            }
2102
2103            /**
2104             * This method checks if the thumbnails of the specified image (origId) has been created.
2105             * It will be blocked until the thumbnails are generated.
2106             *
2107             * @param cr ContentResolver used to dispatch queries to MediaProvider.
2108             * @param origId Original image id associated with thumbnail of interest.
2109             * @param groupId the id of group to which this request belongs
2110             * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND
2111             * @param options this is only used for MINI_KIND when decoding the Bitmap
2112             * @return A Bitmap instance. It could be null if the original image associated with
2113             *         origId doesn't exist or memory is not enough.
2114             */
2115            public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId,
2116                    int kind, BitmapFactory.Options options) {
2117                return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options,
2118                        EXTERNAL_CONTENT_URI, true);
2119            }
2120
2121            /**
2122             * This method cancels the thumbnail request so clients waiting for getThumbnail will be
2123             * interrupted and return immediately. Only the original process which made the getThumbnail
2124             * requests can cancel their own requests.
2125             *
2126             * @param cr ContentResolver
2127             * @param origId original video id
2128             * @param groupId the same groupId used in getThumbnail.
2129             */
2130            public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) {
2131                InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId);
2132            }
2133
2134            /**
2135             * Get the content:// style URI for the image media table on the
2136             * given volume.
2137             *
2138             * @param volumeName the name of the volume to get the URI for
2139             * @return the URI to the image media table on the given volume
2140             */
2141            public static Uri getContentUri(String volumeName) {
2142                return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
2143                        "/video/thumbnails");
2144            }
2145
2146            /**
2147             * The content:// style URI for the internal storage.
2148             */
2149            public static final Uri INTERNAL_CONTENT_URI =
2150                    getContentUri("internal");
2151
2152            /**
2153             * The content:// style URI for the "primary" external storage
2154             * volume.
2155             */
2156            public static final Uri EXTERNAL_CONTENT_URI =
2157                    getContentUri("external");
2158
2159            /**
2160             * The default sort order for this table
2161             */
2162            public static final String DEFAULT_SORT_ORDER = "video_id ASC";
2163
2164            /**
2165             * The data stream for the thumbnail
2166             * <P>Type: DATA STREAM</P>
2167             */
2168            public static final String DATA = "_data";
2169
2170            /**
2171             * The original image for the thumbnal
2172             * <P>Type: INTEGER (ID from Video table)</P>
2173             */
2174            public static final String VIDEO_ID = "video_id";
2175
2176            /**
2177             * The kind of the thumbnail
2178             * <P>Type: INTEGER (One of the values below)</P>
2179             */
2180            public static final String KIND = "kind";
2181
2182            public static final int MINI_KIND = 1;
2183            public static final int FULL_SCREEN_KIND = 2;
2184            public static final int MICRO_KIND = 3;
2185
2186            /**
2187             * The width of the thumbnal
2188             * <P>Type: INTEGER (long)</P>
2189             */
2190            public static final String WIDTH = "width";
2191
2192            /**
2193             * The height of the thumbnail
2194             * <P>Type: INTEGER (long)</P>
2195             */
2196            public static final String HEIGHT = "height";
2197        }
2198    }
2199
2200    /**
2201     * Uri for querying the state of the media scanner.
2202     */
2203    public static Uri getMediaScannerUri() {
2204        return Uri.parse(CONTENT_AUTHORITY_SLASH + "none/media_scanner");
2205    }
2206
2207    /**
2208     * Name of current volume being scanned by the media scanner.
2209     */
2210    public static final String MEDIA_SCANNER_VOLUME = "volume";
2211
2212    /**
2213     * Name of the file signaling the media scanner to ignore media in the containing directory
2214     * and its subdirectories. Developers should use this to avoid application graphics showing
2215     * up in the Gallery and likewise prevent application sounds and music from showing up in
2216     * the Music app.
2217     */
2218    public static final String MEDIA_IGNORE_FILENAME = ".nomedia";
2219
2220    /**
2221     * Get the media provider's version.
2222     * Applications that import data from the media provider into their own caches
2223     * can use this to detect that the media provider changed, and reimport data
2224     * as needed. No other assumptions should be made about the meaning of the version.
2225     * @param context Context to use for performing the query.
2226     * @return A version string, or null if the version could not be determined.
2227     */
2228    public static String getVersion(Context context) {
2229        Cursor c = context.getContentResolver().query(
2230                Uri.parse(CONTENT_AUTHORITY_SLASH + "none/version"),
2231                null, null, null, null);
2232        if (c != null) {
2233            try {
2234                if (c.moveToFirst()) {
2235                    return c.getString(0);
2236                }
2237            } finally {
2238                c.close();
2239            }
2240        }
2241        return null;
2242    }
2243
2244}
2245