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