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