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