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