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