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