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