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