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